title MENU
.z80
aseg
;
; Originally published in Creative Computing, December 1979
; by James J. Frantz who wrote this on May 31, 1979.
; Converted to Z80 code by Frank J. Wancho, August, 1980, and
; removed the BASIC dependencies - i.e. .COM files are the only
; file type examined and displayed.
;
; This program is designed to be automatically executed
; by CP/M immediately after a cold (or warm) boot.  This
; program then displays the contents of the disk in a
; menu fashion.  All files of specified type are sorted and
; displayed alphabetically in four columns.  The user then
; selects the desired program by its menu number.  The
; selected program is then run.
;
PAGE
	org	0100h
;
; First, the CP/M "Search" command is used to find the
; file of the specified type.  The pointer to the File
; Control Block is put in <DE>, and the command number is
; put in <C>.  The File Control Block is pre-constructed to
; the form '????????XXX0'.  The XXX is the specified file
; 'type', and the '?' force a match to any file name of that
; file type.
;
start	equ	$
	ld	sp,stack	; Set up a stack
again	equ	$
	ld	c,17		; 'Search First' Comand
;
; This next routine sorts the file names as they are found
; on the disk directory.  A name is read from the disk and
; its location is found in the directory table by comparing
; alphabetically.
;
sortlp	equ	$
	ld	de,srcfcb	; Point File Control Block
	call	bdos		; Use CP/M entry point
;
; CP/M returns the disk address of the next match in <A>.
; This is a value between 0 and 64, or -1 if no match was
; found.  Test for -1 and quit when no more files of the
; specified file 'type' are found on the disk.  The disk
; address is converted to a pointer to the file name
; within the sector by multiplying by 32 and adding the
; base address of the sector.
;
	cp	255		; Test for -1
	jp	z,assign	; Print empty menu
	rrca			; This is the same as
	rrca			; 5 ADD A's
	rrca
	and	60h		; Mask correct bits
	add	a,80h		; Add base address (0080h)
	ld	e,a		; Put pointer in <DE>
	ld	d,0		; as a 16 bit value
	ld	hl,dirtab	; Point to start of table of
				; sorted names
	inc	de		; Point past erase field
cmplop	equ	$
	push	de		; Save pointer to next
				; entry from disk directory
	ld	c,8		; Length of compare
	push	hl		; Save pointer to table
cmp1	equ	$
	ld	a,(de)		; Get trial name char
	cp	(hl)		; Match?
	jr	nz,endcmp	; If not, try next entry
	inc	hl		; Advance pointers
	inc	de
	dec	c
	jr	nz,cmp1		; Keep testing
endcmp	equ	$
	pop	bc		; Restore table pointer
	jr	c,insnam	; Directory name goes in
				; front of the current table
				; if lower (CY=1)
	ld	hl,14		; Length of table entry
	add	hl,bc		; <HL> to next table entry
	pop	de		; Recover trial name point
	jr	cmplop		; Loop again
;
; This next portion makes room in the directory table for
; the new entry by moving all alphabetically higher names
; upward in memory.
;
insnam	equ	$
	ld	hl,filcnt	; Count the number of files
	inc	(hl)		; to be displayed
	ld	hl,(eot)	; Get pointer to table end
	ex	de,hl
	ld	hl,14		; Distance to move
	add	hl,de		; <HL> point destination
	ld	(eot),hl	; Save the new End of Table
	inc	hl
	inc	de
;
moveup	equ	$
	dec	de
	dec	hl
	ld	a,(de)		; Get byte to move
	ld	(hl),a		; Put in new spot
	ld	a,c		; Test for done
	cp	e		; <BC>=<DE>?
	jr	nz,moveup
	ld	a,b
	cp	d
	jr	nz,moveup
	pop	hl		; Recover pointer
	ld	c,8
	call	blkmov		; Insert name in table
;
; The menu number field is inserted in the directory table
; at this point but the actual menu number will be assigned
; after all the files are sorted.
;
	ld	hl,menbuf	; Point menu number block
	ld	c,6		; Length of move
	call	blkmov		; Insert text in table
;
; The command number for subsequent searches of the disk
; directory must be altered to cause CP/M to search from
; where it left off.
;
	ld	c,18		; 'Search Next' command
	jp	sortlp
PAGE
;
;
; This is the second major portion of the program.  At this
; point, all files have been inserted in the directory table
; in alphabetical order.  Now the menu numbers are assigned
; and inserted in the proper place in preparation for
; display on the terminal.
;
assign	equ	$
	ld	a,(filcnt)
	ld	b,a		; Save in <B>
	push	af		; and on stack
	ld	c,0		; Initial file number
	ld	hl,dirtab+11	; Point first file number
	ld	de,13		; Offset to other numbers
numfil	equ	$
	ld	a,c		; Put file number in <A>
	add	a,1		; Increment
	daa			; Decimal convert
	ld	c,a		; Resave in <C>
	rrca			; Get tens digit into
	rrca			; proper place
	rrca
	rrca
	and	0fh		; and mask
	jr	z,useblk	; Suppress leading zero by
	add	a,10h		; add either 20H (ASCII ' ')
useblk	equ	$
	add	a,' '		; or 20H + 10H for numeric
	ld	(hl),a		; Put in text stream
	ld	a,c		; Get units portion
	and	0fh		; Mask off tens portion
	add	a,'0'		; Convert to ASCII
	inc	hl
	ld	(hl),a
	add	hl,de		; Repeat until all files
	djnz	numfil		; are sequentially numbered
;
	pop	af		; Get FILCNT from stack
	push	af		; and save again for later
;
; This algorithym ensures the columns are as even in length
; as possible.  Don't worry, it works.
;
	add	a,nbrcol-1
	ld	b,-1		; <B> accumulates quotient
				; So set to -1 for at least
				; one pass thru gives 0
divx	equ	$
	inc	b
	sub	nbrcol		; Divide (FILCNT+3) by
				; four to get OFSET1
	jp	p,divx
	add	a,nbrcol	; Subtracted once too much
				; so add it back in
	ld	hl,ofset1
	ld	(hl),b		; Insert OFSET1 into table
	inc	hl		; Point OFSET2 location
	jr	nz,setof2	; Same as OFSET2 if non-
				; zero remainder
	dec	b		; Else OFSET2=OFSET1-1
setof2	equ	$
	ld	(hl),b		; Put OFSET2 in table
	inc	hl		; Point OFFSET for Col. 3
	dec	a		; Test for remainder of 1
	jr	nz,setof3	; If remainder <> 1, use
				; OFSET3=OFSET2
	dec	b		; Else OFSET3=OFSET2-1
setof3	equ	$
	ld	(hl),b		; Offset to Col. 4
;
; Now that the offsets for the columns have been determined
; the actual print out can be made
;
reprt	equ	$
	pop	af		; Recover FILCNT
reprt1	equ	$
	push	af		; Save again for later use
	ld	(filcnt),a	; Save for counting
	ld	a,scrhgt	; Set for video display size
	ld	(lincnt),a
	call	clear		; Clear screen
	ld	de,hdg
	call	print
;
	ld	hl,dirtab-14	; Point dummy 0th entry
;
prtlin	equ	$
	push	hl		; Save base address
	ld	de,ofset0	; Point offset table
	ld	a,nbrcol	; 4 columns per line
prtnam	equ	$
	ld	(colcnt),a	; Save count of columns
	push	hl		; Save current name pointer
	push	de		; Save offset table pointer
;
	ld	de,trplsp	; Print 3 blanks
	call	print		; Use CP/M facility
	pop	de		; Get offset table pointer
	pop	hl		; Get name pointer
	ld	a,(de)		; Get offset value
;
; The offset value is the number of file names from the
; current name pointer to move the print buffer.
;
	ld	bc,14		; Each name is 14 long
mult14	equ	$
	add	hl,bc		; Add 14 for each offset
	dec	a		; Until offset = 0
	jr	nz,mult14
	push	hl		; Save new name pointer
	push	de		; Save offset pointer
	ex	de,hl		; Point name to print w/<DE>
	call	print		; Print the file name and
				; its menu number
;
tesfin	equ	$
	ld	hl,filcnt	; See if done printing
	dec	(hl)		; by testing count of files
	pop	de		; Get offset pointer
	pop	hl		; Get pointer to last name
	jr	z,finish	; No more to print
	inc	de		; Advance offset pointer
	ld	a,(colcnt)
	dec	a		; See if columns left = 0
	jr	nz,prtnam	; Print another same line
	call	crlf		; Move to next line
	pop	hl		; Get base of previous line
	ld	de,14		; Add offset
	add	hl,de
	jr	prtlin
;
; The file names and their menu numbers have been printed.
; This next loop outputs sufficient linefeeds to put the
; heading at the top of a 16 line video display (thereby
; clearing the screen), and puts the request for user
; selection at the bottom of the screen.
;
;
finish	equ	$
	pop	hl		; Unjunk stack
lfloop	equ	$
	call	crlf
	jp	p,lfloop	; Omit this line if desired
	ld	de,prompt	; Point instruction msg
	call	print		; Again CP/M prints message
	ld	de,inbuf
	ld	a,10		; Ten characters max
	ld	(de),a
	ld	c,10		; 'Read Buffer' Command
	call	bdos
;
; On return from BDOS line input function, the digits
; typed by the user are in the buffer at INBUF+2.
; Convert to binary
;
	ld	hl,inbuf+1	; Point character count
	ld	a,(hl)		; Get it and see if > 2
	cp	3
	jr	nc,reprt	; Reprint the menu
	ld	c,a		; Count of digits to <C>
	ld	b,0		; Zero <B>
;
getnum	equ	$
	inc	hl		; Point ASCII digit
	ld	a,(hl)		; Get it
	call	ascbin		; Convert to binary
	jr	c,reprt		; Redisplay on error
	dec	c
	jr	nz,getnum
;
; <B> has the menu number.  To be sure this is still a legal menu
; request, compare with FILCNT (still in stack).
;
	pop	af		; Recover file count
	cp	b		; FILCNT-request number
	jp	c,reprt1	; Redisplay menu if illegal
;
;
	ld	de,14		; Increment between names
	ld	hl,dirtab-14	; Point dummy 0th entry
finame	equ	$
	add	hl,de		; Add offset <B> times
	djnz	finame
;
; At this point <HL> points to the selected file name.  Now find
; the address of CP/M so the proper command name and the selected
; filename can be put into the command buffer.
;
	ex	de,hl		; Save pointer to file name
	ld	hl,(6)		; Get BDOS entry point
	ld	bc,-ccplen	; Offset to start of CP/M
	add	hl,bc
	push	hl		; Save CP/M entry point
				; on stack for branch.
	ld	bc,7		; Offset to command buffer
	add	hl,bc		; <HL> points place to put
				; name of .COM file to be executed.
	ld	a,8		; Get default length of file name
	ld	(hl),a		; Store in CCP
	inc	hl
	push	de		; Save pointer to file name
	ex	de,hl		; <DE> points command buffer
;
; Since the scan pointer is not reset by reentry into CP/M,
; the scan pointer must be reset by this program.  The scan
; pointer is stored by CP/M at the end of the command buffer.
;
	ld	hl,128		; Offset to end of cmd buff
				; where pointer is stored
	add	hl,de		; <HL> points storage place
	ld	(hl),e		; Update buffer pointer to
	inc	hl		; to start of the command
	ld	(hl),d		; buff so CP/M will read.
;
; (The insertion of the BASIC command name goes here.  Removed
; from original version.)
;
	pop	hl		; Point selected file name
	ld	c,8		; Length of file name
	call	blkmov
;
; (The insertion of file type goes here if your version of BASIC
; requires it.)
;
	xor	a		; Need a zero at end
	ld	(de),a		; of command line
;
; The address of CP/M is on the stack, so a simple RETurn
; will execute CP/M and in turn execute 'filename'.
;
	ret
;
; Subroutines
;
blkmov	equ	$
	ld	a,(hl)
	ld	(de),a
	inc	de
	inc	hl
	dec	c
	jr	nz,blkmov
	ret
;
ascbin	equ	$
	sub	'0'		; Subtract ASCII bias
	cp	9+1		; Be sure it's numeric
	ccf
	ret	c		; Set CY=1 if illegal
	ld	d,a		; Save in <D>
	ld	a,b		; Get previous result
	rlca			; Multipy by 2
	rlca			; then 4
	rlca			; then 8
	add	a,b		; then 9
	ret	c		; out of bounds
	add	a,b		; Then finally by 10
	ret	c		; CY=1 always means error
	add	a,d		; Add in new result
	ld	b,a		; Save in <B>
	ret
;
crlf	equ	$
	ld	de,crlfmsg
	call	print
	ld	hl,lincnt
	dec	(hl)
	ret
;
print	equ	$
	ld	c,9		; Buffer print command
	call	bdos		; CP/M prints heading
	ret
;
clear	equ	$
	ld	de,clrs
	call	print
	ret
;
crlfmsg	equ	$
	defm	0dh,0ah,'$'
clrs	equ	$
	defm	30,30,'$'	; Clear Screen
hdg	equ	$
	defm	9,9,9,'     MENU',0dh,0ah,0ah,'$'
;
prompt	equ	$
	defm	'Enter MENU Number and press RETURN: $'
;
trplsp	equ	$
	defm	'   $'
;
menbuf	equ	$
	defm	' -  0$'
;
;
ofset0	equ	$
	defb	1
ofset1	equ	$
	defm	0,0,0
;
eot	equ	$
	defw	dirtab
;
filcnt	equ	$
	defb	0
colcnt	equ	$
	defb	4
lincnt	equ	$
	defb	0
;
srcfcb	equ	$
	defm	0,'????????COM',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
;
dirtab	equ	$
	defb	-1		; Force first file to be put here
;
stack	equ	dirtab+64*14+30
;
inbuf	equ	stack
;
; Equates
;
bdos	equ	5
nbrcol	equ	4
ccplen	equ	3106h-2900h
scrsiz	equ	24
scrhgt	equ	scrsiz-4
	end	start
